package it.polimi.traveldream.ejb.eaos;

import java.util.Date;

import java.util.List;

import it.polimi.traveldream.ejb.dtocreator.BaseProductDTOCreator;
import it.polimi.traveldream.ejb.entities.Excursion;
import it.polimi.traveldream.ejb.entities.Hotel;
import it.polimi.traveldream.ejb.entities.Transport;
import it.polimi.traveldream.ejbclient.dtos.BaseProductDTO;
import it.polimi.traveldream.ejbclient.dtos.ExcursionDTO;
import it.polimi.traveldream.ejbclient.dtos.HotelDTO;
import it.polimi.traveldream.ejbclient.dtos.TransportDTO;
import it.polimi.traveldream.ejbclient.eaos.BaseProductServiceEAO;
import it.polimi.traveldream.utils.enums.BaseProductType;
import it.polimi.traveldream.utils.enums.UserRole;
import it.polimi.traveldream.utils.exceptions.JustInUseException;
import it.polimi.traveldream.utils.exceptions.NotCoherentException;
import it.polimi.traveldream.utils.exceptions.NotSuchProductException;

import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

/**
 * Session Bean implementation class BaseProductService
 */
@Stateless
public class BaseProductService implements BaseProductServiceEAO {
	@PersistenceContext
	private EntityManager entityManager;
	private final int CONDITION_OVERPAST = 0;
	private final int CONDITION_AVAILABLE = 1;
	private final int CONDITION_JUST_IN_PACKAGE = 2;
	private final int QUERY_FOR_AVAILABLE_BY_DATE = 3;
	private final int QUERY_FOR_AVAILABLE_BY_OVERPAST = 4;
	
	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@SuppressWarnings("unchecked")
	@Override
	public List<TransportDTO> getAvailableTransports(Date start, Date end, int reservations) {
		Query q = createBaseProductQuery(TransportDTO.class, QUERY_FOR_AVAILABLE_BY_DATE, start, end, reservations, null, null);
		return q != null ? (List<TransportDTO>) BaseProductDTOCreator.createBaseProductList(q.getResultList())
				: null;}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@SuppressWarnings("unchecked")
	@Override
	public List<HotelDTO> getAvailableHotels(Date start, Date end, int reservations) {
		Query q = createBaseProductQuery(HotelDTO.class, QUERY_FOR_AVAILABLE_BY_DATE, start, end, reservations, null, null);
		return q != null ? (List<HotelDTO>) BaseProductDTOCreator.createBaseProductList(q.getResultList())
				: null;
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@SuppressWarnings("unchecked")
	@Override
	public List<ExcursionDTO> getAvailableExcursions(Date start, Date end, int reservations) {
		Query q = createBaseProductQuery(ExcursionDTO.class, QUERY_FOR_AVAILABLE_BY_DATE, start, end, reservations, null, null);
		return q != null ? (List<ExcursionDTO>) BaseProductDTOCreator.createBaseProductList(q.getResultList())
				: null;
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@SuppressWarnings("unchecked")
	@Override
	public List<TransportDTO> getTransports(boolean overpast) {
		Query q = createBaseProductQuery(TransportDTO.class, QUERY_FOR_AVAILABLE_BY_OVERPAST, null, null, null, overpast, null);
		return q != null ? (List<TransportDTO>) BaseProductDTOCreator.createBaseProductList(q.getResultList())
				: null;
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@SuppressWarnings("unchecked")
	@Override
	public List<HotelDTO> getHotels(boolean overpast) {
		Query q = createBaseProductQuery(HotelDTO.class, QUERY_FOR_AVAILABLE_BY_OVERPAST, null, null, null, overpast, null);
		return q != null ? (List<HotelDTO>) BaseProductDTOCreator.createBaseProductList(q.getResultList())
				: null;
	}
	
	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@SuppressWarnings("unchecked")
	@Override
	public List<ExcursionDTO> getExcursions(boolean overpast) {
		Query q = createBaseProductQuery(ExcursionDTO.class, QUERY_FOR_AVAILABLE_BY_OVERPAST, null, null, null, overpast, null);
		return q != null ? (List<ExcursionDTO>) BaseProductDTOCreator.createBaseProductList(q.getResultList())
				: null;
	}
	
	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	public HotelDTO getHotel(int hotelId) throws NotSuchProductException {
		Hotel hotel = entityManager.find(Hotel.class, hotelId);
		if(hotel != null) {
			return (HotelDTO) BaseProductDTOCreator.createBaseProduct(hotel);
		} else throw new NotSuchProductException();
	}
	
	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	public TransportDTO getTransport(int transportId) throws NotSuchProductException {
		Transport transport = entityManager.find(Transport.class, transportId);
		if(transport != null) {
			return (TransportDTO) BaseProductDTOCreator.createBaseProduct(transport);
		} else throw new NotSuchProductException();
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	public ExcursionDTO getExcursion(int excursionId) throws NotSuchProductException {
		Excursion excursion = entityManager.find(Excursion.class, excursionId);
		if(excursion != null) {
			return (ExcursionDTO) BaseProductDTOCreator.createBaseProduct(excursion);
		} else throw new NotSuchProductException();
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public boolean isAvailableFor(int productId, BaseProductType type, int reservations)
			throws NotSuchProductException {
		return checkConditionOnProduct(CONDITION_AVAILABLE, productId, type, reservations);
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public boolean isOverpast(int productId, BaseProductType type) throws NotSuchProductException{	
		return checkConditionOnProduct(CONDITION_OVERPAST, productId, type, null);
	}

	@RolesAllowed({UserRole._EMPLOYEE})
	@Override
	public int addProduct(BaseProductDTO product) throws NotCoherentException{
		if(checkProductCoherence(product)) {
			product.setReservations(0);
			product.setOverpast(false);
			if(product instanceof TransportDTO) {
				Transport newTransport = new Transport((TransportDTO) product);
				entityManager.persist(newTransport);
				entityManager.flush();
				return newTransport.getId();
			} else if(product instanceof HotelDTO) {
				Hotel newHotel = new Hotel((HotelDTO) product);
				entityManager.persist(newHotel);
				entityManager.flush();
				return newHotel.getId();
			} else if(product instanceof ExcursionDTO) {
				Excursion newExcursion = new Excursion((ExcursionDTO) product);
				entityManager.persist(newExcursion);
				entityManager.flush();
				return newExcursion.getId();
			} else {
				throw new NotCoherentException();
			}
		} else throw new NotCoherentException();
	}

	@RolesAllowed({UserRole._EMPLOYEE})
	@Override
	public void modifyProduct(BaseProductDTO product)
			throws NotCoherentException, NotSuchProductException, JustInUseException {
		if(product instanceof TransportDTO) {
			Transport transport = entityManager.find(Transport.class, product.getId());
			entityManager.refresh(transport);
			if(transport != null) {
				if(!justInPackage(transport.getId(), BaseProductType.TRANSPORT) && !transport.getOverpast()) {
					if(checkProductCoherence(product)) {
						entityManager.merge(new Transport((TransportDTO) product));
					} else throw new NotCoherentException();
				} else throw new JustInUseException();
			} else throw new NotSuchProductException();
		} else if(product instanceof HotelDTO) {
			Hotel hotel = entityManager.find(Hotel.class, product.getId());
			entityManager.refresh(hotel);
			if(hotel != null) {
				if(!justInPackage(hotel.getId(), BaseProductType.HOTEL) && !hotel.getOverpast()) {
					if(checkProductCoherence(product)) {
						entityManager.merge(new Hotel((HotelDTO) product));
					} else throw new NotCoherentException();
				} else throw new JustInUseException();
			} else throw new NotSuchProductException();
		} else if(product instanceof ExcursionDTO) {
			Excursion excursion = entityManager.find(Excursion.class, product.getId());
			entityManager.refresh(excursion);
			if(excursion != null) {
				if(!justInPackage(excursion.getId(), BaseProductType.EXCURSION) && !excursion.getOverpast()) {
					if(checkProductCoherence(product)) {
						entityManager.merge(new Excursion((ExcursionDTO) product));
					} else throw new NotCoherentException();
				} else throw new JustInUseException();
			} else throw new NotSuchProductException();
		}
	}

	@RolesAllowed({UserRole._EMPLOYEE})
	@Override
	public void deleteProduct(int productId, BaseProductType type)
			throws NotSuchProductException, JustInUseException {
		switch(type) {
		case HOTEL:
			Hotel hotel = entityManager.find(Hotel.class, productId);
			entityManager.refresh(hotel);
			if(hotel != null) {
				if(!justInPackage(productId, type) && !hotel.getOverpast()) {
					entityManager.remove(hotel);
				} else throw new JustInUseException();
			} else throw new NotSuchProductException();
			break;
		case TRANSPORT:
			Transport transport = entityManager.find(Transport.class, productId);
			entityManager.refresh(transport);
			if(transport != null) {
				if(!justInPackage(productId, type) && !transport.getOverpast()) {
					entityManager.remove(transport);
				} else throw new JustInUseException();
			} else throw new NotSuchProductException();
			break;
		case EXCURSION:
			Excursion excursion = entityManager.find(Excursion.class, productId);
			entityManager.refresh(excursion);
			if(excursion != null) {
				if(!justInPackage(productId, type) && !excursion.getOverpast()) {
					entityManager.remove(excursion);
				} else throw new JustInUseException();
			} else throw new NotSuchProductException();
			break;
		default:
			throw new NotSuchProductException();
		}
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public boolean justInPackage(int productId, BaseProductType type)
			throws NotSuchProductException {
		return checkConditionOnProduct(CONDITION_JUST_IN_PACKAGE, productId, type, null);
		
	}

	private boolean checkProductCoherence(BaseProductDTO product) {
		if(isCommonBaseProductDataCoherent(product)) {
			if(product instanceof TransportDTO) {
				// future possible conditions on transport data
				return true;
			} else if(product instanceof HotelDTO) {
				// future possible conditions on hotel data
				return true;
			} else if(product instanceof ExcursionDTO) {
				// future possible conditions on excursion data
				return true;
			}
		}
		return false;
	}

	private boolean isCommonBaseProductDataCoherent(BaseProductDTO product) {
		Date currentDate = new Date();
		return currentDate.before(product.getStartTrip())
				&& product.getStartTrip().before(product.getEndTrip());
	}
	
	private <T> Query createBaseProductQuery(Class<T> c, int queryType, Date start,
			Date end, Integer reservations, Boolean overpast, String locality) {
		try {
			T instance = c.newInstance();
			Query q = null;
			if(instance instanceof TransportDTO) {
				q = createTransportQuery(queryType);
	        } else if(instance instanceof HotelDTO) {
	        	q = createHotelQuery(queryType);
	        } else if(instance instanceof ExcursionDTO) {
	        	q = createExcursionQuery(queryType);
		    }
			switch(queryType) {
			case QUERY_FOR_AVAILABLE_BY_DATE:
				q.setParameter("start", start);
				q.setParameter("end", end);
				q.setParameter("reservations", reservations);
				break;
			case QUERY_FOR_AVAILABLE_BY_OVERPAST:
				q.setParameter("overpast", overpast);
				break;
			}
			return q;
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private Query createTransportQuery(int queryType) {
		Query q = null;
		switch(queryType) {
		case QUERY_FOR_AVAILABLE_BY_DATE:
			q = entityManager.createNamedQuery(Transport.FIND_TRANSPORTS_AVAILABLE);
			break;
		case QUERY_FOR_AVAILABLE_BY_OVERPAST:
			q = entityManager.createNamedQuery(Transport.FIND_TRANSPORTS);
			break;
		}
		return q;
	}
	
	private Query createHotelQuery(int queryType) {
		Query q = null;
		switch(queryType) {
		case QUERY_FOR_AVAILABLE_BY_DATE:
			q = entityManager.createNamedQuery(Hotel.FIND_HOTELS_AVAILABLE);
			break;
		case QUERY_FOR_AVAILABLE_BY_OVERPAST:
			q = entityManager.createNamedQuery(Hotel.FIND_HOTELS);
			break;
		}
		return q;
	}

	private Query createExcursionQuery(int queryType) {
		Query q = null;
		switch(queryType) {
		case QUERY_FOR_AVAILABLE_BY_DATE:
			q = entityManager.createNamedQuery(Excursion.FIND_EXCURSIONS_AVAILABLE);
			break;
		case QUERY_FOR_AVAILABLE_BY_OVERPAST:
			q = entityManager.createNamedQuery(Excursion.FIND_EXCURSIONS);
			break;
		}
		return q;
	}
	
	private boolean checkConditionOnProduct(int condition, int productId,
			BaseProductType type, Integer reservations) throws NotSuchProductException {
		switch(type) {
		case HOTEL:
			Hotel hotel = entityManager.find(Hotel.class, productId);
			if(hotel != null) {
				switch(condition) {
				case CONDITION_AVAILABLE:
					return hotel.getMaxAvailability() - hotel.getReservations() >= reservations; 
				case CONDITION_OVERPAST:
					return hotel.getOverpast();
				case CONDITION_JUST_IN_PACKAGE:
					return hotel.getReservations() > 0 || !hotel.getTravelpackages().isEmpty();
				}
			} else throw new NotSuchProductException();
		case TRANSPORT:
			Transport transport = entityManager.find(Transport.class, productId);
			if(transport != null) {
				switch(condition) {
				case CONDITION_AVAILABLE:
					return transport.getMaxAvailability() - transport.getReservations() >= reservations; 
				case CONDITION_OVERPAST:
					return transport.getOverpast();
				case CONDITION_JUST_IN_PACKAGE:
					return transport.getReservations() > 0 || !transport.getTravelpackages().isEmpty();
				}
			} else throw new NotSuchProductException();
		case EXCURSION:
			Excursion excursion = entityManager.find(Excursion.class, productId);
			if(excursion != null) {
				switch(condition) {
				case CONDITION_AVAILABLE:
					return excursion.getMaxAvailability() - excursion.getReservations() >= reservations; 
				case CONDITION_OVERPAST:
					return excursion.getOverpast();
				case CONDITION_JUST_IN_PACKAGE:
					return excursion.getReservations() > 0 || !excursion.getTravelpackages().isEmpty();
				}
			} else throw new NotSuchProductException();
		default:
			throw new NotSuchProductException();
		}
	}
	
}
